//-----------------------------------------------------------------------
// <author>Paul Long</author>
//-----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Microsoft.NetworkMonitor;
using NetworkAddresses;
using NMCaptureData;
using TopUserCharts;
using TopUserExceptions;
using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;

namespace NMTopUsers
{

    /// <summary>
    /// Direction of network traffic.
    /// </summary>
    public enum Direction
    {
        /// <summary>
        ///  Data flows from the 'source (from)' to the 'to (destination)' address.
        /// </summary>
        Forward,

        /// <summary>
        ///  Data flows from the 'to (destination)' address to the 'source (from)' address.
        /// </summary>
        Reverse
    }

    /// <summary>
    /// Class that interfaces with the NMAPI.  It collects the trace data and parses.
    /// It will also populate the grid, bar chart, and piechart elements.
    /// </summary>
    internal class NetworkMonitorData : IDisposable
    {
        #region States

        /// <summary>
        /// Used to convert 8 Bits to a byte.
        /// </summary>
        private const uint BitsPerByte = 8;

        /// <summary>
        /// Keeps track of if errors occured while loading the parsers.
        /// </summary>
        private static bool errors;

        /// <summary>
        /// List of errors collected from NmLoadNplParsers
        /// </summary>
        private static string errorList;

        /// <summary>
        /// Callback for parser loading.
        /// </summary>
        private static ParserCallbackDelegate errorCallBack = new ParserCallbackDelegate(ParserCallback);

        /// <summary>
        /// List to hold endpoints or conversations.
        /// </summary>
        private static List<NetworkElement> machines;

        /// <summary>
        /// Count of IPv4 addresses.
        /// </summary>
        private uint addrIPv4Count;

        /// <summary>
        /// Count of IPv6 addresses
        /// </summary>
        private uint addrIPv6Count;

        /// <summary>
        /// Count of Ethernet addresses
        /// </summary>
        private uint ethernetCount;

        /// <summary>
        /// TotalBytes over all.
        /// </summary>
        private uint totalBytes;

        /// <summary>
        /// Total frames captured taking into account filter.
        /// </summary>
        private uint totalFrames;

        /// <summary>
        /// IPv4 Source Address ID for NmAddField
        /// </summary>
        private uint addrIPv4SourceID;

        /// <summary>
        /// IPv4 Destination Address ID for NmAddField
        /// </summary>
        private uint addrIPv4DestID;

        /// <summary>
        /// IPv6 Source Address ID for NmAddField
        /// </summary>
        private uint addrIPv6SourceID;

        /// <summary>
        /// IPv6 Destination Address ID for NmAddField
        /// </summary>
        private uint addrIPv6DestID;

        /// <summary>
        /// Ethernet Destination Address ID for NmAddField
        /// </summary>
        private uint addrEthSourceID;

        /// <summary>
        /// Ethernet Destination Address ID for NmAddField
        /// </summary>
        private uint addrEthDestID;

        /// <summary>
        /// Wifi AP->Client Source Address
        /// </summary>
        private uint addrWifiAPToClientSourceID;

        /// <summary>
        /// Wifi AP->Client Dest Address
        /// </summary>
        private uint addrWifiAPToClientDestID;

        /// <summary>
        /// Wifi Client->AP Source Address
        /// </summary>
        private uint addrWifiClientToAPSourceID;

        /// <summary>
        /// Wifi Client->AP Dest Address
        /// </summary>
        private uint addrWifiClientToAPDestID;

        /// <summary>
        /// Disposed flag
        /// </summary>
        private bool disposed;

        /// <summary>
        /// Collect IPv4 information.
        /// </summary>
        private bool showIPv4;

        /// <summary>
        /// Collect IPv6 information
        /// </summary>
        private bool showIPv6;

        /// <summary>
        /// Collect Ethernet information
        /// </summary>
        private bool showEthernet;

        /// <summary>
        /// Whether we are Endpoint or Conversation mode.
        /// </summary>
        private TopUsers.OperationMode mode;

        /// <summary>
        /// Display Filter passed in from command line
        /// </summary>
        private string combinedFilter;

        /// <summary>
        /// NPL Set NMAPI handle
        /// </summary>
        private IntPtr parserNPLSetHandle = IntPtr.Zero;

        /// <summary>
        /// Capture file NMAPI handle
        /// </summary>
        private IntPtr captureFileHandle = IntPtr.Zero;

        /// <summary>
        /// Paser Configuration NMAPI handle
        /// </summary>
        private IntPtr parserConfigHandle = IntPtr.Zero;

        /// <summary>
        /// Parser NMAPI handle
        /// </summary>
        private IntPtr parserHandle = IntPtr.Zero;

        /// <summary>
        /// Capture File Name that is passed in.
        /// </summary>
        private string captureFileName;

        /// <summary>
        /// Number of frames to parse
        /// </summary>
        private uint captureFrameCount;

        /// <summary>
        /// Display Filter ID
        /// </summary>
        private uint filterId;

        /// <summary>
        /// The starting and ending frame times of data included in the grid.
        /// </summary>
        private ulong startingFrameTime;
        private ulong endingFrameTime;

        #endregion

        #region Contructors

        /// <summary>
        /// Initializes a new instance of the NetworkMonitorData class.
        /// Loads the parser and initializes the filter and whether this is
        /// Conversation or Endpoint mode.
        /// </summary>
        /// <param name="filter">The filter provide from the command line.</param>
        /// <param name="mode">Conversation or Endpoint mode.</param>
        public NetworkMonitorData(string filter, TopUsers.OperationMode mode)
        {
            uint errno;

            this.combinedFilter = filter;

            this.mode = mode;

            errno = Microsoft.NetworkMonitor.NetmonAPI.NmLoadNplParser(null, NmNplParserLoadingOption.NmAppendRegisteredNplSets,
                                                                       errorCallBack, System.IntPtr.Zero, out this.parserNPLSetHandle);
            
            // Error 57 is the normal error returned for parser errors.  We will
            // handle this gracefully by displaying the errors.            
            if (errno != 0 && errno != 0x57)
            {
                throw new ParsingException("NmLoadNplParser(...) failed, errno = " + errno);
            }

            if (errno == 0x57)
            {
                errors = true;
            }
        }

        /// <summary>
        /// Finalizes an instance of the NetworkMonitorData class.
        /// </summary>
        ~NetworkMonitorData()
        {
            Microsoft.NetworkMonitor.NetmonAPI.NmCloseHandle(this.parserHandle);
            Microsoft.NetworkMonitor.NetmonAPI.NmCloseHandle(this.parserConfigHandle);
            Microsoft.NetworkMonitor.NetmonAPI.NmCloseHandle(this.parserNPLSetHandle);
            Microsoft.NetworkMonitor.NetmonAPI.NmCloseHandle(this.captureFileHandle);
            Microsoft.NetworkMonitor.NetmonAPI.NmApiClose();

            this.Dispose(false);
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets the list of parser build errors.
        /// </summary>
        public static string GetErrors
        {
            get
            {
                return errorList;
            }
        }

        /// <summary>
        /// Gets a value indicating whether an error occured while loading the parsers.
        /// </summary>
        public static bool ErrorsExist
        {
            get
            {
                return errors;
            }
        }

        /// <summary>
        /// Gets total number of addresses based on which
        /// options have been selected for addresses to display.
        /// </summary>
        private uint TotalAddressCount
        {
            get
            {
                uint totalAddressCount = 0;
                if (this.showEthernet)
                {
                    totalAddressCount += this.ethernetCount;
                }

                if (this.showIPv4)
                {
                    totalAddressCount += this.addrIPv4Count;
                }

                if (this.showIPv6)
                {
                    totalAddressCount += this.addrIPv6Count;
                }

                return totalAddressCount;
            }
        }

        public string DisplayedFramesStartEndTimes
        {
            get
            {
                DateTime dtStart = FrameTimeToDateTimeConverter(this.startingFrameTime);
                DateTime dtEnd = FrameTimeToDateTimeConverter(this.endingFrameTime);
                string s = string.Format("Frame times: {0} to {1}", dtStart.ToString("G"), dtEnd.ToString("G"));
                return s;
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Dispose routine
        /// </summary>
        public void Dispose()
        {
            if (!this.disposed)
            {
                this.Dispose(true);
                GC.SuppressFinalize(this);
            }
        }

        /// <summary>
        /// Converts frame time to local time.
        /// </summary>
        /// <param name="frameTimestamp">The frame time.</param>
        /// <returns>Local time as a DateTime.</returns>
        private DateTime FrameTimeToDateTimeConverter(ulong frameTimestamp)
        {
            DateTime dt = DateTime.FromFileTimeUtc((long)frameTimestamp);

            return dt;
        }

        /// <summary>
        /// Dumps the data as text.  This is for testing.
        /// </summary>
        /// <param name="dumpFile">File name to dump data to.</param>
        public void DumpData(string dumpFile)
        {
            StreamWriter sr = File.CreateText(dumpFile);

            foreach (NetworkElement m in machines)
            {
                m.DumpRow(sr, this.mode);
                foreach (NetworkElement subm in m.SubNetworkElements)
                {
                    subm.DumpRow(sr, this.mode);
                }
            }

            sr.Close();
        }

        /// <summary>
        /// Fills a pie chart control with all of the informaiton based on what
        /// is selected in the UI.  It then returns a Pie chart back to the caller.
        /// </summary>
        /// <param name="enableEthernet">Is ethernet enabled in UI?</param>
        /// <param name="enableIPv4">Is IPv4 enabled in UI?</param>
        /// <param name="enableIPv6">Is IPv6 enabled in UI?</param>
        /// <returns>PieChart control</returns>
        public PieChart CreatePieChart(bool enableEthernet, bool enableIPv4, bool enableIPv6)
        {
            if (enableEthernet && (enableIPv4 || enableIPv6))
            {
                double[] etherY = new double[this.ethernetCount];
                string[] etherX = new string[this.ethernetCount];

                double[] addrIPY = new double[this.TotalAddressCount];
                string[] addrIPX = new string[this.TotalAddressCount];

                int i = 0;
                int j = 0;
                foreach (NetworkElement m in machines)
                {
                    if (m.IsEthernetAddress() && this.showEthernet)
                    {
                        etherX[i] = m.DisplayAddress(this.mode);
                        etherY[i] = m.TotalBytes;

                        uint totalSubBytes = m.TotalBytes;

                        if (m.SubNetworkElements.Count > 0)
                        {
                            foreach (NetworkElement subm in m.SubNetworkElements)
                            {
                                if ((subm.IsIPv4Address() && this.showIPv4) || (subm.IsIPv6Address() && this.showIPv6))
                                {
                                    addrIPY[j] = subm.TotalBytes;
                                    addrIPX[j] = subm.DisplayAddress(this.mode);
                                    j++;
                                    totalSubBytes -= subm.TotalBytes;
                                }
                            }
                        }

                        if (totalSubBytes > 0)
                        {
                            addrIPY[j] = totalSubBytes;
                            addrIPX[j] = "None";
                            j++;
                        }

                        i++;
                    }
                }

                return new PieChart(etherY, etherX, addrIPY, addrIPX);
            }
            else
            {
                double[] addressTotals = new double[this.TotalAddressCount];
                string[] addressValues = new string[this.TotalAddressCount];
                
                int i = 0;
                foreach (NetworkElement m in machines)
                {
                    if (m.IsEthernetAddress() && this.showEthernet)
                    {
                        addressTotals[i] = m.TotalBytes;
                        addressValues[i] = m.DisplayAddress(this.mode);
                        i++;
                    }

                    foreach (NetworkElement subm in m.SubNetworkElements)
                    {
                        if ((subm.IsIPv4Address() && this.showIPv4) ||
                            (subm.IsIPv6Address() && this.showIPv6))
                        {
                            addressTotals[i] = subm.TotalBytes;
                            addressValues[i] = subm.DisplayAddress(this.mode);
                            i++;
                        }
                    }
                }

                return new PieChart(addressTotals, addressValues);
            }
        }

        /// <summary>
        /// Fills a bar chart control with all of the information based on what
        /// is selected in the UI. Then it returns a bar chart control back to the caller.
        /// </summary>
        /// <param name="enableEthernet">Is ethernet enabled in UI?</param>
        /// <param name="enableIPv4">Is IPv4 enabled in UI?</param>
        /// <param name="enableIPv6">Is IPv6 enabled in UI?</param>
        /// <returns>Bar Chart Control</returns>
        public BarChart CreateBarChart(bool enableEthernet, bool enableIPv4, bool enableIPv6)
        {
            if (enableEthernet && (enableIPv4 || enableIPv6))
            {
                double[] ethernetTotals = new double[this.ethernetCount];

                string[] ethernetAddrs = new string[this.ethernetCount];

                double[] addrIPv4Totals = new double[this.ethernetCount];

                string[] addrIPv4Addrs = new string[this.ethernetCount];

                double[] addrIPv6Totals = new double[this.ethernetCount];

                string[] addrIPv6Addrs = new string[this.ethernetCount];

                int i = 0;
                foreach (NetworkElement m in machines)
                {
                    ethernetTotals[i] = m.TotalBytes;
                    ethernetAddrs[i] = m.DisplayAddress(this.mode);

                    // Init each to blank in case it's not there
                    addrIPv4Totals[i] = 0;
                    addrIPv4Addrs[i] = "No IPv4";
                    addrIPv4Totals[i] = 0;
                    addrIPv4Addrs[i] = "No IPv6";

                    foreach (NetworkElement subm in m.SubNetworkElements)
                    {
                        if (subm.IsIPv4Address() && this.showIPv4)
                        {
                            addrIPv4Totals[i] = subm.TotalBytes;
                            addrIPv4Addrs[i] = subm.DisplayAddress(this.mode);

                            ethernetAddrs[i] += "\n" + subm.DisplayAddress(this.mode);
                        }
                        else if (subm.IsIPv6Address() && this.showIPv6)
                        {
                            addrIPv6Totals[i] = subm.TotalBytes;
                            addrIPv6Addrs[i] = subm.DisplayAddress(this.mode);

                            ethernetAddrs[i] += "\n" + subm.DisplayAddress(this.mode);
                        }
                    }

                    i++;
                }

                if (this.showIPv4 && this.showIPv6)
                {
                    return new BarChart(ethernetTotals, ethernetAddrs, addrIPv4Totals, addrIPv4Addrs, addrIPv6Totals, addrIPv6Addrs);
                }
                else if (this.showIPv4)
                {
                    return new BarChart(ethernetTotals, ethernetAddrs, addrIPv4Totals, addrIPv4Addrs);
                }
                else
                {
                    return new BarChart(ethernetTotals, ethernetAddrs, addrIPv6Totals, addrIPv6Addrs);
                }
            }
            else
            {
                uint totalNetworkElements = 0;

                if (!this.showEthernet)
                {
                    if (this.showIPv4)
                    {
                        totalNetworkElements += this.addrIPv4Count;
                    }

                    if (this.showIPv6)
                    {
                        totalNetworkElements += this.addrIPv6Count;
                    }
                }
                else
                {
                    totalNetworkElements = this.ethernetCount;
                }

                double[] addressTotals = new double[totalNetworkElements];

                string[] addressNames = new string[totalNetworkElements];

                int i = 0;
                foreach (NetworkElement m in machines)
                {
                    if (!this.showEthernet)
                    {
                        foreach (NetworkElement subm in m.SubNetworkElements)
                        {
                            if ((subm.IsIPv4Address() && this.showIPv4) ||
                                (subm.IsIPv6Address() && this.showIPv6))
                            {
                                addressTotals[i] = subm.TotalBytes;
                                addressNames[i] = subm.DisplayAddress(this.mode);
                                i++;
                            }
                        }
                    }
                    else
                    {
                        addressTotals[i] = m.TotalBytes;
                        addressNames[i] = m.DisplayAddress(this.mode);
                        i++;
                    }
                }

                return new BarChart(addressTotals, addressNames);
            }
        }

        /// <summary>
        /// Checks whether or not there are multiple IPv4 or IPv6 address for a single Ethernet address.
        /// This is not supported when drawing a bar chart.
        /// </summary>
        /// <param name="doIPv4">Do we want to check for IPv4 addresses?</param>
        /// <param name="doIPv6">Do we want to check for IPv6 addresses?</param>
        /// <returns>True if there is an address mapping.</returns>
        public bool CheckAddressMapping(bool doIPv4, bool doIPv6)
        {
            if ((doIPv4 && (this.addrIPv4Count > this.ethernetCount)) || (doIPv6 && (this.addrIPv6Count > this.ethernetCount)))
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// Fills in the Data View Grid passed in by the user.
        /// </summary>
        /// <param name="grid">Grid to fill in</param>
        /// <param name="showIPv4">Should we show IPv4 addresses?</param>
        /// <param name="showIPv6">Should we show IPv6 addresses?</param>
        /// <param name="showEthernet">Should we show ethernet addresses?</param>
        /// <param name="tree">Show with tree formatting?</param>
        public void FillGrid(DataGridView grid, bool showIPv4, bool showIPv6, bool showEthernet, bool tree)
        {
            grid.Rows.Clear();

            this.showIPv4 = showIPv4;
            this.showIPv6 = showIPv6;
            this.showEthernet = showEthernet;

            ulong overallStartTime = ulong.MaxValue;
            ulong overallEndTime = 0;

            uint treeOrderCnt = 1;
            uint totalBytesRcvd = 0;
            uint totalBytesSent = 0;
            uint totalFramesRcvd = 0;
            uint totalFramesSent = 0;

            // Now iterate through the list of endpoints or conversations and add to Grid.
            foreach (NetworkElement m in machines)
            {
                if ((m.IsIPv4Address() && this.showIPv4) ||
                    (m.IsIPv6Address() && this.showIPv6) ||
                    (m.IsEthernetAddress() && this.showEthernet))
                {
                    if (overallStartTime == ulong.MaxValue)
                    {
                        overallStartTime = m.SentStartTime;
                    }

                    if (overallEndTime < m.ReceiveEndTime)
                    {
                        overallEndTime = m.ReceiveEndTime;
                    }

                    totalBytesRcvd += m.RecvBytes;
                    totalBytesSent += m.SentBytes;

                    totalFramesRcvd += m.RecvFrames;
                    totalFramesSent += m.SentFrames;

                    grid.Rows.Add(m.GetRow(this.mode, treeOrderCnt, false));
                    treeOrderCnt++;
                }

                foreach (NetworkElement subm in m.SubNetworkElements)
                {
                    if ((subm.IsIPv4Address() && this.showIPv4) ||
                        (subm.IsIPv6Address() && this.showIPv6) ||
                        (subm.IsEthernetAddress() && this.showEthernet))
                    {
                        if (overallStartTime == ulong.MaxValue)
                        {
                            overallStartTime = m.SentStartTime;
                        }

                        if (overallEndTime < m.ReceiveEndTime)
                        {
                            overallEndTime = m.ReceiveEndTime;
                        }

                        totalBytesRcvd += subm.RecvBytes;
                        totalBytesSent += subm.SentBytes;

                        totalFramesRcvd += subm.RecvFrames;
                        totalFramesSent += subm.SentFrames;

                        grid.Rows.Add(subm.GetRow(this.mode, treeOrderCnt, tree));
                        treeOrderCnt++;
                    }
                }
            }

            ulong timeDiff = overallEndTime - overallStartTime;

            double overallKbps = CalcThroughput.Throughput(this.totalBytes, timeDiff);
            double rcvdKbps = CalcThroughput.Throughput(totalBytesRcvd, timeDiff); 
            double sentKbps = CalcThroughput.Throughput(totalBytesSent, timeDiff);

            // this.totalBytes and this.totalFrames may include frames that were excluded since they
            // were not part of a displayed row. So don't use them to display "Total Bytes" and "Total Frames".
            uint bytesTotal = totalBytesSent + totalBytesRcvd;
            uint framesTotal = totalFramesSent + totalFramesRcvd;

          if (this.mode == TopUsers.OperationMode.Endpoint)
            {
                object[] summary_row = { "*Summary", "Summary", 
                                         bytesTotal, (double) 100, framesTotal, 
                                         totalBytesSent, (double) 100, totalFramesSent, sentKbps, 
                                         totalBytesRcvd, (double) 100, totalFramesRcvd, rcvdKbps, timeDiff, (uint)0 };
                grid.Rows.Add(summary_row);
            }
            else // Show conversations.
            {
                // Calculate the percentages for each row.
                double rowValue;
                double pct;
                for(int n = 0; n < grid.Rows.Count; n++)
                {
                    // Percent of total bytes.
                    rowValue = (uint)(grid.Rows[n].Cells[(int)TopUsers.GridColumnName.TotalBytes].Value);
                    if (bytesTotal <= 0)
                        pct = 0;
                    else
                        pct = 100.0 * rowValue / bytesTotal;
                    grid.Rows[n].Cells[(int) TopUsers.GridColumnName.PctTotalBytes].Value = pct;

                    // Percent of sent bytes.
                    rowValue = (uint)(grid.Rows[n].Cells[(int)TopUsers.GridColumnName.BytesSent].Value);
                    if (totalBytesSent <= 0)
                        pct = 0;
                    else
                        pct = 100.0 * rowValue / totalBytesSent;
                    grid.Rows[n].Cells[(int)TopUsers.GridColumnName.PctBytesSent].Value = pct;

                    // Percent of received bytes.
                    rowValue = (uint)(grid.Rows[n].Cells[(int)TopUsers.GridColumnName.BytesRcvd].Value);
                    if (totalBytesRcvd <= 0)
                        pct = 0;
                    else
                        pct = 100.0 * rowValue / totalBytesRcvd;
                    grid.Rows[n].Cells[(int)TopUsers.GridColumnName.PctBytesRcvd].Value = pct;
                }

                object[] summary_row = { "*Summary", "Summary", 
                                         bytesTotal, (double) 100, framesTotal, 
                                         totalBytesSent, (double) 100, totalFramesSent, sentKbps, 
                                         totalBytesRcvd, (double) 100, totalFramesRcvd, rcvdKbps, timeDiff, (uint)0 };
                grid.Rows.Add(summary_row);
            }
        }

        /// <summary>
        /// Attempts to load the parsers, open the capture and parse the capture. It 
        /// will update the passed in status label and status bar as it loads.
        /// </summary>
        /// <param name="fileName">File name of capture to load.</param>
        /// <param name="statusLabel">Tool Strip label control.</param>
        /// <param name="progressBar">Progress bar control.</param>
        /// <param name="tp">Top Users class</param>
        /// <returns>True if capture loads</returns>
        public bool LoadCapture(string fileName, ToolStripStatusLabel statusLabel, ToolStripProgressBar progressBar, TopUsers tp)
        {
            uint errno;

            this.captureFileName = fileName;

            if (errors)
            {
                return false;
            }

            machines = new List<NetworkElement>();

            if (statusLabel != null)
            {
                statusLabel.Text = "Loading Parsers...";
            }

            if (progressBar != null)
            {
                progressBar.Value = progressBar.Minimum + (progressBar.Maximum - progressBar.Minimum)/2;
            }

            if (tp != null)
            {
                tp.Refresh();
            }

            try
            {
                this.CreateParser();
            }
            catch (ParsingException)
            {
                throw;
            }

            if (progressBar != null)
            {
                progressBar.Value = progressBar.Maximum;
            }

            errno = NetmonAPI.NmOpenCaptureFile(this.captureFileName, out this.captureFileHandle);
            if (errno != 0)
            {
                string error = string.Format(CultureInfo.CurrentCulture, "Error loading file {0}", this.captureFileName);
                throw new ParsingException(error);
            }

            if (statusLabel != null)
            {
                statusLabel.Text = "Loading Capture...";
            }

            if (tp != null)
            {
                tp.Refresh();
            }

            this.ParseData(progressBar);

            return true;
        }

        /// <summary>
        /// Dispose with boolean flag
        /// </summary>
        /// <param name="disposing">Indicates if we are currently disposing.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                this.disposed = true;

                if (disposing)
                {
                    NetmonAPI.NmCloseHandle(this.parserNPLSetHandle);
                    NetmonAPI.NmCloseHandle(this.captureFileHandle);
                    NetmonAPI.NmCloseHandle(this.parserConfigHandle);
                    NetmonAPI.NmCloseHandle(this.parserHandle);
                }

                // Free the unmanaged resource ...
                this.parserNPLSetHandle = IntPtr.Zero;
                this.captureFileHandle = IntPtr.Zero;
                this.parserConfigHandle = IntPtr.Zero;
                this.parserHandle = IntPtr.Zero;
            }
        }

        /// <summary>
        /// Parser callback function for loading NPL.
        /// </summary>
        /// <param name="callerContext">Context passed in by caller</param>
        /// <param name="statusCode">Status code of current message.</param>
        /// <param name="description">Message Text</param>
        /// <param name="type">Type of message, error, warning, informational.</param>
        private static void ParserCallback(IntPtr callerContext, uint statusCode, string description, Microsoft.NetworkMonitor.NmCallbackMsgType type)
        {
            errorList = errorList + description + "\n";
            if (type == Microsoft.NetworkMonitor.NmCallbackMsgType.Error)
            {
                errors = true;
            }
            else
            {
                errors = false;
            }
        }

        /// <summary>
        /// Attempt to load the frame parser config, add filters and fields, and then create frame parser.
        /// </summary>
        /// <returns>Returns true if loading was succesful.</returns>
        private bool CreateParser()
        {
            uint errno;

            errno = NetmonAPI.NmCreateFrameParserConfiguration(this.parserNPLSetHandle, errorCallBack, IntPtr.Zero, out this.parserConfigHandle);
            if (errno != 0)
            {
                throw new ParsingException("NmCreateFrameParserConfiguration(...) failed, errno = " + errno);
            }

            errno = NetmonAPI.NmConfigConversation(this.parserConfigHandle, NmConversationConfigOption.None, true);
            if (errno != 0)
            {
                throw new ParsingException("NmConfigConversation(...) failed, errno = " + errno);
            }

            if (!string.IsNullOrEmpty(this.combinedFilter))
            {
                errno = NetmonAPI.NmAddFilter(this.parserConfigHandle, this.combinedFilter, out this.filterId);
                if (errno != 0)
                {
                    throw new ParsingException("NmAddFilter(...) failed, errnor = " + errno);
                }
            }

            try
            {
                this.AddDataFields();
                errno = NetmonAPI.NmCreateFrameParser(this.parserConfigHandle, out this.parserHandle, NmFrameParserOptimizeOption.ParserOptimizeNone);
                if (errno != 0)
                {
                    throw new ParsingException("NNmCreateFrameParser(...) failed, errno = " + errno);
                }
            }
            catch (ParsingException)
            {
                throw;
            }

            return true;
        }

        /// <summary>
        /// Adds the data fields we are interested in.
        /// </summary>
        private void AddDataFields()
        {
            uint errno;

            try
            {
                errno = NetmonAPI.NmAddField(this.parserConfigHandle, "ipv4.SourceAddress", out this.addrIPv4SourceID);
                if (errno != 0)
                {
                    throw new ParsingException("NmAddField(...) failed, errnor = " + errno);
                }

                errno = NetmonAPI.NmAddField(this.parserConfigHandle, "ipv4.DestinationAddress", out this.addrIPv4DestID);
                if (errno != 0)
                {
                    throw new ParsingException("NmAddField(...) failed, errnor = " + errno);
                }

                errno = NetmonAPI.NmAddField(this.parserConfigHandle, "ipv6.SourceAddress", out this.addrIPv6SourceID);
                if (errno != 0)
                {
                    throw new ParsingException("NmAddField(...) failed, errnor = " + errno);
                }

                errno = NetmonAPI.NmAddField(this.parserConfigHandle, "ipv6.DestinationAddress", out this.addrIPv6DestID);
                if (errno != 0)
                {
                    throw new ParsingException("NmAddField(...) failed, errnor = " + errno);
                }

                errno = NetmonAPI.NmAddField(this.parserConfigHandle, "ethernet.SourceAddress", out this.addrEthSourceID);
                if (errno != 0)
                {
                    throw new ParsingException("NmAddField(...) failed, errnor = " + errno);
                }

                errno = NetmonAPI.NmAddField(this.parserConfigHandle, "ethernet.DestinationAddress", out this.addrEthDestID);
                if (errno != 0)
                {
                    throw new ParsingException("NmAddField(...) failed, errnor = " + errno);
                }

                errno = NetmonAPI.NmAddField(this.parserConfigHandle, "WiFi.Data.APToClient.SA", out this.addrWifiAPToClientSourceID);
                if (errno != 0)
                {
                    throw new ParsingException("NmAddField(...) failed, errnor = " + errno);
                }

                errno = NetmonAPI.NmAddField(this.parserConfigHandle, "WiFi.Data.APToClient.DA", out this.addrWifiAPToClientDestID);
                if (errno != 0)
                {
                    throw new ParsingException("NmAddField(...) failed, errnor = " + errno);
                }

                errno = NetmonAPI.NmAddField(this.parserConfigHandle, "WiFi.Data.ClientToAP.SA", out this.addrWifiClientToAPSourceID);
                if (errno != 0)
                {
                    throw new ParsingException("NmAddField(...) failed, errnor = " + errno);
                }

                errno = NetmonAPI.NmAddField(this.parserConfigHandle, "WiFi.Data.ClientToAP.DA", out this.addrWifiClientToAPDestID);
                if (errno != 0)
                {
                    throw new ParsingException("NmAddField(...) failed, errnor = " + errno);
                }
            }
            catch (ParsingException)
            {
                throw;
            }
        }

        /// <summary>
        /// Adds or creates or new conversation/endpoint for ethernet.  
        /// </summary>
        /// <param name="from">The from address or endpiont.</param>
        /// <param name="to">If conversation mode, this is the to address.</param>
        /// <param name="frameLength">Length of the frame in bytes.</param>
        /// <param name="frameTimeStamp">Time stamp of the frame.</param>
        private void AddOrCreate(Address from, Address to, uint frameLength, ulong frameTimeStamp)
        {
            if (frameTimeStamp < this.startingFrameTime)
            {
                this.startingFrameTime = frameTimeStamp;
            }
            if (frameTimeStamp > this.endingFrameTime)
            {
                this.endingFrameTime = frameTimeStamp;
            }

            if (this.mode == TopUsers.OperationMode.Endpoint)
            {
                // Search for the 'from' address in the list of endpoints.
                NetworkElement nm = machines.Find(m => m.MatchFromAddress(from));

                if (nm == null)
                {
                    // The 'from' address was not found in the list of endpoints, so add it.
                    nm = new NetworkElement(from, Direction.Forward, frameLength, frameTimeStamp);
                    if (from is MachineAddress)
                    {
                        this.ethernetCount++;
                    }

                    if (from is IPv4Address)
                    {
                        this.addrIPv4Count++;
                    }

                    if (from is IPv6Address)
                    {
                        this.addrIPv6Count++;
                    }

                    machines.Add(nm);
                }
                else
                {
                    // An existing 'from' endpoint was found, so add this frame to that endpoint.
                    nm.AddFrame(Direction.Forward, frameLength, frameTimeStamp);
                }

                // Search for the 'to' address in the list of endpoints.
                nm = machines.Find(m => m.MatchFromAddress(to));

                if (nm == null)
                {
                    // The 'to' address was not found in the list of endpoints, so add it.
                    nm = new NetworkElement(to, Direction.Reverse, frameLength, frameTimeStamp);
                    if (from is MachineAddress)
                    {
                        this.ethernetCount++;
                    }

                    if (to is IPv4Address)
                    {
                        this.addrIPv4Count++;
                    }

                    if (to is IPv6Address)
                    {
                        this.addrIPv6Count++;
                    }

                    machines.Add(nm);
                }
                else
                {
                    // An existing 'to' endpoint was found, so add this frame to that endpoint.
                    nm.AddFrame(Direction.Reverse, frameLength, frameTimeStamp);
                }
            }
            else // Show conversations.
            {
                // Search for this forward conversation in the list of conversations.
                NetworkElement nm = machines.Find(m => (m.MatchFromAddress(from) && m.MatchToAddress(to)));

                if (nm != null)
                {
                    // An existing forward conversation was found, so add this frame to that conversation.
                    // Direction.Forward causes the traffic to be added to the 'Sent' statistics.
                    nm.AddFrame(Direction.Forward, frameLength, frameTimeStamp);
                }
                else
                {
                    // Search for this reverse conversation in the list of conversations.
                    nm = machines.Find(
                        m => (m.MatchFromAddress(to) && m.MatchToAddress(from)));

                    if (nm != null)
                    {
                        // An existing reverse conversation was found, so add this frame to that conversation.
                        // Direction.Reverse causes the traffic to be added to the 'Received' statistics.
                        nm.AddFrame(Direction.Reverse, frameLength, frameTimeStamp);
                    }
                    else
                    {
                        // No existing conversation was found, so add a new conversation to the list.
                        nm = new NetworkElement(from, to, Direction.Forward, frameLength, frameTimeStamp);
                        if (from is MachineAddress)
                        {
                            this.ethernetCount++;
                        }

                        if (from is IPv4Address)
                        {
                            this.addrIPv4Count++;
                        }

                        if (from is IPv6Address)
                        {
                            this.addrIPv6Count++;
                        }

                        machines.Add(nm);
                    }
                }
            }
        }

        /// <summary>
        /// Adds an address with parent information.
        /// </summary>
        /// <param name="from">>The from address or endpoint.</param>
        /// <param name="to">If conversation mode, this is the to address.</param>
        /// <param name="frameLength">Length of the frame in bytes.</param>
        /// <param name="frameTimeStamp">Time stamp of the frame.</param>
        /// <param name="ethernetFromAddress">Ethernet Parent From address.</param>
        /// <param name="ethernetToAddress">Ethernet Parent To Address</param>
        private void AddOrCreateWithEthernet(Address from, Address to, uint frameLength, ulong frameTimeStamp, Address ethernetFromAddress, 
                                             Address ethernetToAddress)
        {
            bool isIPv4Address = from is IPv4Address;
            bool isIPv6Address = from is IPv6Address;

            if (frameTimeStamp < this.startingFrameTime)
            {
                this.startingFrameTime = frameTimeStamp;
            }
            if (frameTimeStamp > this.endingFrameTime)
            {
                this.endingFrameTime = frameTimeStamp;
            }

            if (this.mode == TopUsers.OperationMode.Endpoint)
            {
                // From direction.
                NetworkElement nm = machines.Find(m => m.MatchFromAddress(ethernetFromAddress));

                if (nm == null)
                {
                    throw new ParsingException("AddOrCreateWithEthernet didn't find an ethernet address.");
                }

                NetworkElement subnm = nm.FindNetworkElementByIP(from);
                if (subnm == null)
                {
                    if (isIPv4Address)
                    {
                        this.addrIPv4Count++;
                    }

                    if (isIPv6Address)
                    {
                        this.addrIPv6Count++;
                    }

                    nm.AddNetworkElementIP(from, Direction.Forward, frameLength, frameTimeStamp);
                }
                else
                {
                    subnm.AddFrame(Direction.Forward, frameLength, frameTimeStamp);
                }

                // To direction.
                nm = machines.Find(m => m.MatchFromAddress(ethernetToAddress));

                if (nm == null)
                {
                    throw new ParsingException("AddOrCreateWithEthernet didn't find an ethernet address.");
                }

                subnm = nm.FindNetworkElementByIP(to);
                if (subnm == null)
                {
                    if (isIPv4Address)
                    {
                        this.addrIPv4Count++;
                    }

                    if (isIPv6Address)
                    {
                        this.addrIPv6Count++;
                    }

                    nm.AddNetworkElementIP(to, Direction.Reverse, frameLength, frameTimeStamp);
                }
                else
                {
                    subnm.AddFrame(Direction.Reverse, frameLength, frameTimeStamp);
                }
            }
            else // Show conversations.
            {
                // Search for this forward conversation in the list of conversations.
                NetworkElement nm = machines.Find(m => (m.MatchFromAddress(ethernetFromAddress) && m.MatchToAddress(ethernetToAddress)));

                if (nm != null)
                {
                    // An existing forward conversation was found.
                    // Direction.Forward causes the traffic to be added to the 'Sent' statistics.
                    NetworkElement subnm = nm.FindNetworElementByIPPair(from, to);

                    if (subnm == null)
                    {
                        // No existing forward subnet conversation was found, so add one.
                        subnm = new NetworkElement(from, to, Direction.Forward, frameLength, frameTimeStamp);
                        if (isIPv4Address)
                        {
                            this.addrIPv4Count++;
                        }

                        if (isIPv6Address)
                        {
                            this.addrIPv6Count++;
                        }

                        nm.SubNetworkElements.Add(subnm);
                    }
                    else
                    {
                        // An existing forward subnet conversation was found, so add this frame to that conversation.
                        subnm.AddFrame(Direction.Forward, frameLength, frameTimeStamp);
                    }
                }
                else
                {
                    // Search for this reverse conversation in the list of conversations.
                    nm = machines.Find(m => (m.MatchFromAddress(ethernetToAddress) && m.MatchToAddress(ethernetFromAddress)));

                    if (nm != null)
                    {
                        // An existing reverse conversation was found.
                        // Direction.Reverse causes the traffic to be added to the 'Received' statistics.
                        NetworkElement subnm = nm.FindNetworElementByIPPair(to, from);

                        if (subnm == null)
                        {
                            // No existing reverse subnet conversation was found, so add one.
                            subnm = new NetworkElement(to, from, Direction.Reverse, frameLength, frameTimeStamp);
                            if (isIPv4Address)
                            {
                                this.addrIPv4Count++;
                            }

                            if (isIPv6Address)
                            {
                                this.addrIPv6Count++;
                            }

                            nm.SubNetworkElements.Add(subnm);
                        }
                        else
                        {
                            // An existing reverse subnet conversation was found, so add this frame to that conversation.
                            subnm.AddFrame(Direction.Reverse, frameLength, frameTimeStamp);
                        }
                    }
                    else
                    {
                        // No existing conversation was found.
                        throw new ParsingException("AddOrCreateWithEthernet didn't find an ethernet address.");
                    }
                }
            }
        }

        /// <summary>
        /// Parses the frames in the capture file and stores the information.
        /// </summary>
        /// <param name="progressBar">Progress bar control</param>
        private void ParseData(ToolStripProgressBar progressBar)
        {
            this.totalBytes = 0;
            this.startingFrameTime = ulong.MaxValue;
            this.endingFrameTime = ulong.MinValue;

            uint errno = NetmonAPI.NmGetFrameCount(this.captureFileHandle, out this.captureFrameCount);
            if (errno != 0)
            {
                throw new ParsingException("NmGetFrameCount(...)failed : " + errno);
            }

            if (progressBar != null)
            {
                progressBar.Visible = true;
                progressBar.Minimum = 0;
                progressBar.Maximum = (int)this.captureFrameCount;
                progressBar.Value = 0;
            }

            for (uint i = 0; i < this.captureFrameCount; i++)
            {
                if (i % 20 == 0 && progressBar != null)
                {
                    progressBar.Value = (int)i;
                    Application.DoEvents();
                }

                IntPtr rawFrameHandle = IntPtr.Zero;

                errno = NetmonAPI.NmGetFrame(this.captureFileHandle, i, out rawFrameHandle);
                if (errno != 0)
                {
                    throw new ParsingException("NmGetFrame(" + i.ToString(CultureInfo.CurrentCulture) + ")failed : " + errno);
                }

                IntPtr parsedFrameHandle = IntPtr.Zero;
                IntPtr insertedRawFrameHandle = IntPtr.Zero;

                errno = NetmonAPI.NmParseFrame(
                                                this.parserHandle,
                                                rawFrameHandle,
                                                i,
                                                (NmFrameParsingOption.UseFrameNumberParameter | NmFrameParsingOption.FieldDisplayStringRequired),
                                                out parsedFrameHandle,
                                                out insertedRawFrameHandle);

                if (errno != 0)
                {
                    throw new ParsingException("NmParseFrame(...) failed, status = " + errno);
                }

                bool passedFilter = false;
                if (!string.IsNullOrEmpty(this.combinedFilter))
                {
                    errno = NetmonAPI.NmEvaluateFilter(parsedFrameHandle, this.filterId, out passedFilter);
                    if (errno != 0)
                    {
                        throw new ParsingException("NmEvaluateFilter failed, status = " + errno);
                    }
                }
                else
                {
                    passedFilter = true;
                }

                if (passedFilter)
                {
                    uint frameLength;
                    errno = NetmonAPI.NmGetRawFrameLength(rawFrameHandle, out frameLength);
                    if (errno != 0)
                    {
                        throw new ParsingException("NMGetRawFrameLength failed, status = " + errno);
                    }

                    // Total frames and bytes can be larger than the sum of totals from the rows of the displayed
                    // grid. This is because some frames contain data that doesn't make it into the rows of the
                    // grid.
                    this.totalFrames++;
                    this.totalBytes += frameLength;

                    ulong frameTimeStamp;
                    errno = NetmonAPI.NmGetFrameTimeStamp(parsedFrameHandle, out frameTimeStamp);
                    if (errno != 0)
                    {
                        throw new ParsingException("NmGetFrameTimeStamp failed, status = " + errno);
                    }

                    MachineAddress ethernetSrc = new MachineAddress(parsedFrameHandle, this.addrEthSourceID);
                    MachineAddress ethernetDst = new MachineAddress(parsedFrameHandle, this.addrEthDestID);

                    if (ethernetSrc.IsValid() && ethernetDst.IsValid())
                    {
                        this.AddOrCreate(ethernetSrc, ethernetDst, frameLength, frameTimeStamp);
                    }
                    else
                    {
                        // If it's not Ethernet, it's got to be something.  Check wireless.
                        ethernetSrc = new MachineAddress(parsedFrameHandle, this.addrWifiAPToClientSourceID);
                        ethernetDst = new MachineAddress(parsedFrameHandle, this.addrWifiAPToClientDestID);

                        if (ethernetSrc.IsValid() && ethernetDst.IsValid())
                        {
                            this.AddOrCreate(ethernetDst, ethernetSrc, frameLength, frameTimeStamp);
                        }
                        else
                        {
                            ethernetSrc = new MachineAddress(parsedFrameHandle, this.addrWifiClientToAPSourceID);
                            ethernetDst = new MachineAddress(parsedFrameHandle, this.addrWifiClientToAPDestID);

                            if (ethernetSrc.IsValid() && ethernetDst.IsValid())
                            {
                                this.AddOrCreate(ethernetDst, ethernetSrc, frameLength, frameTimeStamp);
                            }
                        }
                    }

                    // Don't support tunneling.
                    if (ethernetSrc.Display != null && ethernetDst.Display != null)
                    {
                        IPv4Address addrIPv4Src = new IPv4Address(parsedFrameHandle, this.addrIPv4SourceID);
                        IPv4Address addrIPv4Dst = new IPv4Address(parsedFrameHandle, this.addrIPv4DestID);

                        if (addrIPv4Src.IsValid() && addrIPv4Dst.IsValid())
                        {
                            this.AddOrCreateWithEthernet(addrIPv4Src, addrIPv4Dst, frameLength, frameTimeStamp, ethernetSrc, ethernetDst);
                        }

                        IPv6Address addrIPv6Src = new IPv6Address(parsedFrameHandle, this.addrIPv6SourceID);
                        IPv6Address addrIPv6Dst = new IPv6Address(parsedFrameHandle, this.addrIPv6DestID);

                        if (addrIPv6Src.IsValid() && addrIPv6Dst.IsValid())
                        {
                            this.AddOrCreateWithEthernet(addrIPv6Src, addrIPv6Dst, frameLength, frameTimeStamp, ethernetSrc, ethernetDst);
                        }
                    }
                }

                NetmonAPI.NmCloseHandle(parsedFrameHandle);
                NetmonAPI.NmCloseHandle(rawFrameHandle);
            }
        }

        #endregion
    }
}